#include "api_md5.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>

 enum Md5State {
    Md5Ok = 0,
    Md5Failed = 1,
    Md5TokenFaild = 4,
    Md5FileExit = 5,
};

int decodeMd5Json(string &str_json, string &user_name, string &token,
                  string &md5, string &filename) {
    bool res;
    Json::Value root;
    Json::Reader jsonReader;
    res = jsonReader.parse(str_json, root);
    if (!res) {
        LOG_ERROR << "parse md5 json failed ";
        return -1;
    }

    if (root["user"].isNull()) {
        LOG_ERROR << "user null";
        return -1;
    }
    user_name = root["user"].asString();

    if (root["token"].isNull()) {
        LOG_ERROR << "token null";
        return -1;
    }
    token = root["token"].asString();

    if (root["md5"].isNull()) {
        LOG_ERROR << "md5 null";
        return -1;
    }
    md5 = root["md5"].asString();

    if (root["fileName"].isNull()) {
        LOG_ERROR << "fileName null";
        return -1;
    }
    filename = root["fileName"].asString();

    return 0;
}

int encodeMd5Json(int ret, string &str_json) {
    Json::Value root;
    root["code"] = ret;
    Json::FastWriter writer;
    str_json = writer.write(root);
    return 0;
}



void handleDealMd5(const char *user, const char *md5, const char *filename, string &str_json) {
    Md5State md5_state = Md5Failed;
    int ret = 0;
    int file_ref_count = 0;
    char sql_cmd[SQL_MAX_LEN] = {0};

    CDBManager *db_manager = CDBManager::getInstance();
    CDBConn *db_conn = db_manager->GetDBConn("file_system_master");
    AUTO_REL_DBCONN(db_manager, db_conn);
    CacheManager *cache_manager = CacheManager::getInstance();
    CacheConn *cache_conn = cache_manager->GetCacheConn("token");
    AUTO_REL_CACHECONN(cache_manager, cache_conn);

    // 修改点 1：先检查 file_info 表中是否存在相同的 MD5
    sprintf(sql_cmd, "SELECT COUNT(*) FROM file_info WHERE md5 = '%s'", md5);
    LOG_INFO << "执行: " << sql_cmd;
    ret = GetResultOneCount(db_conn, sql_cmd, file_ref_count); // 执行 SQL 语句
    LOG_INFO << "ret: " << ret << ", file_ref_count: " << file_ref_count;

    if (ret == 0 && file_ref_count > 0) { // 有结果，说明文件已存在
        md5_state = Md5Ok; // 秒传成功
        LOG_INFO << "秒传成功，文件已存在";
        goto END;
    }

    // 如果文件不存在，继续后续逻辑
    // sql 语句，获取此 md5 值文件的文件计数器 count
    sprintf(sql_cmd, "SELECT count FROM file_info WHERE md5 = '%s'", md5);
    LOG_INFO << "执行: " << sql_cmd;
    file_ref_count = 0;
    ret = GetResultOneCount(db_conn, sql_cmd, file_ref_count); // 执行 SQL 语句
    LOG_INFO << "ret: " << ret << ", file_ref_count: " << file_ref_count;

    if (ret == 0) { // 有结果，说明文件已存在
        // 查看此用户是否已经有此文件，如果存在说明此文件已上传，无需再上传
        sprintf(sql_cmd,
                "SELECT * FROM user_file_list WHERE user = '%s' AND md5 = '%s' AND file_name = '%s'",
                user, md5, filename);
        LOG_INFO << "执行: " << sql_cmd;
        ret = CheckwhetherHaveRecord(db_conn, sql_cmd); // 检测个人是否有记录
        if (ret == 1) { // 如果有结果，说明此用户已经保存此文件
            LOG_WARN << "user: " << user << "-> filename: " << filename << ", md5: " << md5 << " 已存在";
            md5_state = Md5FileExit; // 此用户已经有该文件了，不能重复上传
            goto END;
        }

        // 修改 file_info 中的 count 字段，+1 （文件引用计数），多了一个用户拥有该文件
        sprintf(sql_cmd, "UPDATE file_info SET count = %d WHERE md5 = '%s'", file_ref_count + 1, md5);
        LOG_INFO << "执行: " << sql_cmd;
        if (!db_conn->ExecutePassQuery(sql_cmd)) {
            LOG_ERROR << sql_cmd << " 操作失败";
            md5_state = Md5Failed; // 更新文件引用计数失败这里也认为秒传失败，宁愿他再次上传文件
            goto END;
        }

        // 2、user_file_list, 用户文件列表插入一条数据
        struct timeval tv;
        struct tm *ptm;
        char time_str[128];

        gettimeofday(&tv, NULL);
        ptm = localtime(&tv.tv_sec);
        strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", ptm);

        sprintf(sql_cmd,
                "INSERT INTO user_file_list(user, md5, create_time, file_name, shared_status, pv) "
                "VALUES ('%s', '%s', '%s', '%s', %d, %d)", user, md5, time_str, filename, 0, 0);
        LOG_INFO << "执行: " << sql_cmd;
        if (!db_conn->ExecuteCreate(sql_cmd)) {
            LOG_ERROR << sql_cmd << " 操作失败";
            md5_state = Md5Failed;
            // 恢复引用计数
            sprintf(sql_cmd, "UPDATE file_info SET count = %d WHERE md5 = '%s'", file_ref_count, md5);
            LOG_INFO << "执行: " << sql_cmd;
            if (!db_conn->ExecutePassQuery(sql_cmd)) {
                LOG_ERROR << sql_cmd << " 操作失败";
            }
            goto END;
        }

        // 查询用户文件数量，用户数量 + 1
        if (CacheIncrCount(cache_conn, FILE_USER_COUNT + string(user)) < 0) {
            LOG_WARN << "CacheIncrCount failed"; // 这个可以在 login 的时候从 mysql 加载
        }

        md5_state = Md5Ok;
    } else { // 没有结果，秒传失败
        LOG_INFO << "秒传失败";
        md5_state = Md5Failed;
        goto END;
    }

END:
    int code = (int)md5_state;
    encodeMd5Json(code, str_json);
}


int ApiMd5(string &post_data, string &str_json) {
    //解析json中信息
    /*
        * {
        user:xxxx,
        token: xxxx,
        md5:xxx,
        fileName: xxx
        }
        */
    string user;
    string md5;
    string token;
    string filename;
    int ret = 0;
    ret = decodeMd5Json(post_data, user, token, md5, filename); //解析json中信息
    if (ret < 0) {
        LOG_ERROR << "decodeMd5Json() err";
        encodeMd5Json((int)Md5Failed, str_json);
        return 0;
    }

    //验证登陆token，成功返回0，失败-1
    ret = VerifyToken(user, token); //
    if (ret == 0) {
        handleDealMd5(user.c_str(), md5.c_str(), filename.c_str(), str_json); //秒传处理
        return 0;
    } else {
        LOG_ERROR << "VerifyToken failed";
        encodeMd5Json(HTTP_RESP_FAIL, str_json); // token验证失败错误码
        return 0;
    }
}
